/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools check point
 */

#include "compare_check_point.h"

#include <memory>
#include <cinttypes>
#include <cmath>
#include <string>
#include <sys/types.h>

#include "hmfs_log.h"
#include "hmfs_utils.h"
#include "io.h"
#include "securec.h"

namespace OHOS {
namespace Hmfs {

enum JournalType {
    JOURNAL_TYPE_NAT,
    JOURNAL_TYPE_SIT,
};

int32_t CompareCheckPoint::CompareCheckPointBinary()
{
    if (ReadCheckPointArea(srcInfo_)) {
        HMFS_ERROR("Failed to read cp area.");
        return -1;
    }

    if (ReadCheckPointArea(dstInfo_)) {
        HMFS_ERROR("Failed to read cp area.");
        return -1;
    }

    if (CompareCheckPointFirstSegment()) {
        HMFS_ERROR("First segment in cp not equal.");
        return -1;
    }

    if (CompareCheckPointSecondSegment()) {
        HMFS_ERROR("Second segment in cp not equal.");
        return -1;
    }

    return 0;
}

int32_t CompareCheckPoint::ReadCheckPointArea(FsData &fsInfo)
{
    fsInfo.cpOffset = GetLeValue(fsInfo.superBlock->checkPointBlockId) * HMFS_BLOCK_SIZE;
    HMFS_INFO("cpOffset = 0x%" PRIx64 ".", fsInfo.cpOffset);

    uint64_t cpBufferSize = (2 << GetLeValue(fsInfo.superBlock->logBlksPerSeg)) * HMFS_BLOCK_SIZE;
    fsInfo.cpBuf = std::make_unique<char[]>(cpBufferSize);
    if (DevRead(fsInfo, fsInfo.cpBuf.get(), fsInfo.cpOffset, cpBufferSize)) {
        HMFS_ERROR("Failed to read cp area.");
        return -1;
    }
    fsInfo.cpPos = fsInfo.cpBuf.get();

    fsInfo.cp = reinterpret_cast<CheckPointData*>(fsInfo.cpBuf.get());
    fsInfo.curSeg[CURSEG_NODE_HOT] = GetLeValue(fsInfo.cp->curNodeSegNo[0]);
    fsInfo.curSeg[CURSEG_NODE_WARM] = GetLeValue(fsInfo.cp->curNodeSegNo[1]);
    fsInfo.curSeg[CURSEG_NODE_COLD] = GetLeValue(fsInfo.cp->curNodeSegNo[2]);
    fsInfo.curSeg[CURSEG_DATA_HOT] = GetLeValue(fsInfo.cp->curDataSegNo[0]);
    fsInfo.curSeg[CURSEG_DATA_WARM] = GetLeValue(fsInfo.cp->curDataSegNo[1]);
    fsInfo.curSeg[CURSEG_DATA_COLD] = GetLeValue(fsInfo.cp->curDataSegNo[2]);

    return 0;
}

int32_t CompareCheckPoint::CompareCheckPointFirstSegment()
{
    if (CompareCheckPointBlock(srcInfo_.cp, dstInfo_.cp)) {
        return -1;
    }

    uint32_t checkPointPayload = GetLeValue(srcInfo_.superBlock->checkPointPayload);
    if (checkPointPayload) {
        if (memcmp(srcInfo_.cpPos, dstInfo_.cpPos, checkPointPayload * HMFS_BLOCK_SIZE)) {
            HMFS_ERROR("checkPointPayload not equal, checkPointPayload = %u", checkPointPayload);
            return -1;
        }
        srcInfo_.cpPos += checkPointPayload * HMFS_BLOCK_SIZE;
        dstInfo_.cpPos += checkPointPayload * HMFS_BLOCK_SIZE;
    }

    if (CompareSumCompact()) {
        return -1;
    }

    if (CompareHotNodeSummary()) {
        return -1;
    }

    if (memcmp(srcInfo_.cpPos, dstInfo_.cpPos, HMFS_BLOCK_SIZE)) {
        HMFS_ERROR("warm node summary not equal, at offset 0x%" PRIx64 "",
            srcInfo_.cpPos - srcInfo_.cpBuf.get() + srcInfo_.cpOffset);
        return -1;
    }
    srcInfo_.cpPos += HMFS_BLOCK_SIZE;
    dstInfo_.cpPos += HMFS_BLOCK_SIZE;

    if (memcmp(srcInfo_.cpPos, dstInfo_.cpPos, HMFS_BLOCK_SIZE)) {
        HMFS_ERROR("cold node summary not equal, at offset 0x%" PRIx64 "",
            srcInfo_.cpPos - srcInfo_.cpBuf.get() + srcInfo_.cpOffset);
        return -1;
    }
    srcInfo_.cpPos += HMFS_BLOCK_SIZE;
    dstInfo_.cpPos += HMFS_BLOCK_SIZE;

    if (CompareCheckPointBlock(reinterpret_cast<CheckPointData*>(srcInfo_.cpPos),
        reinterpret_cast<CheckPointData*>(dstInfo_.cpPos))) {
        return -1;
    }

    uint32_t natBitmapSize = GetLeValue(srcInfo_.superBlock->segmentCountInNAT) / 2 * 512 / 8;
    uint32_t natBitsAreablocks = AlignUpCount(sizeof(uint64_t) + natBitmapSize + natBitmapSize, HMFS_BLOCK_SIZE);

    uint64_t remainderBufLen = BLOCKS_PER_SEGMENT * HMFS_BLOCK_SIZE - (srcInfo_.cpPos - srcInfo_.cpBuf.get()) -
        natBitsAreablocks * HMFS_BLOCK_SIZE;
    srcInfo_.cpPos += remainderBufLen;
    dstInfo_.cpPos += remainderBufLen;

    /*
     * nat bits
     */
#ifndef HMFS_TOOLS_ARM64
    if (CompareNatBit(srcInfo_.cpPos, dstInfo_.cpPos, natBitmapSize, natBitsAreablocks)) {
        return -1;
    }
#endif
    return 0;
}

int32_t CompareCheckPoint::CompareCheckPointSecondSegment()
{
    struct CheckPointData *srcCpData = reinterpret_cast<CheckPointData*>(srcInfo_.cpPos);
    struct CheckPointData *dstCpData = reinterpret_cast<CheckPointData*>(dstInfo_.cpPos);
    if (CompareCheckPointBlock(srcInfo_.cp, dstInfo_.cp)) {
        HMFS_ERROR("CpBlock 1 in second seg not equal");
        return -1;
    }

    uint32_t checkPointPayload = GetLeValue(srcInfo_.superBlock->checkPointPayload);
    if (checkPointPayload) {
        if (memcmp(srcInfo_.cpPos, dstInfo_.cpPos, checkPointPayload * HMFS_BLOCK_SIZE)) {
            HMFS_ERROR("checkPointPayload not equal, checkPointPayload = %u", checkPointPayload);
            return -1;
        }
        srcInfo_.cpPos += checkPointPayload * HMFS_BLOCK_SIZE;
        dstInfo_.cpPos += checkPointPayload * HMFS_BLOCK_SIZE;
    }

    uint64_t emptyBufLen = HMFS_BLOCK_SIZE * 4;
    srcInfo_.cpPos += emptyBufLen;
    dstInfo_.cpPos += emptyBufLen;

    srcCpData = reinterpret_cast<CheckPointData*>(srcInfo_.cpPos);
    dstCpData = reinterpret_cast<CheckPointData*>(dstInfo_.cpPos);
    if (CompareCheckPointBlock(srcInfo_.cp, dstInfo_.cp)) {
        HMFS_ERROR("CpBlock 2 in second seg not equal");
        return -1;
    }

    uint64_t remainderBufLen = BLOCKS_PER_SEGMENT * HMFS_BLOCK_SIZE * 2 - (srcInfo_.cpPos - srcInfo_.cpBuf.get());
    srcInfo_.cpPos += remainderBufLen;
    dstInfo_.cpPos += remainderBufLen;

    return 0;
}

int32_t CompareCheckPoint::CompareCheckPointBlock(CheckPointData *srcCp, CheckPointData *dstCp)
{
#ifdef HMFS_UNIT_TEST_RESIZE
    if (srcCp->userBlockCount == 0 || dstCp->userBlockCount == 0) {
        return 0;
    }
#endif
    COMPARE_NUMBER(srcCp, dstCp, userBlockCount);
    COMPARE_NUMBER(srcCp, dstCp, validBlockCount);
    COMPARE_NUMBER(srcCp, dstCp, reservedSegmentCount);
    COMPARE_NUMBER(srcCp, dstCp, overprovisionSegmentCount);
    COMPARE_NUMBER(srcCp, dstCp, freeSegmentCount);
    for (uint32_t i = 0; i < HMFS_MAX_ACTIVE_NODE_LOGS; i++) {
        COMPARE_NUMBER(srcCp, dstCp, curNodeSegNo[i]);
        COMPARE_NUMBER(srcCp, dstCp, curNodeBlkOffset[i]);
        COMPARE_NUMBER(srcCp, dstCp, curDataSegNo[i]);
        COMPARE_NUMBER(srcCp, dstCp, curDataBlkOffset[i]);
    }
#ifndef HMFS_UNIT_TEST_RESIZE
    COMPARE_NUMBER(srcCp, dstCp, checkPointFlags);
#endif
    COMPARE_NUMBER(srcCp, dstCp, checkPointPackBlockCount);
    COMPARE_NUMBER(srcCp, dstCp, summaryStartBlock);
    COMPARE_NUMBER(srcCp, dstCp, validNodeCount);
    COMPARE_NUMBER(srcCp, dstCp, validInodeCount);
    COMPARE_NUMBER(srcCp, dstCp, nextFreeNodeId);
    COMPARE_NUMBER(srcCp, dstCp, sitVersionBitmapSize);
    COMPARE_NUMBER(srcCp, dstCp, natVersionBitmapSize);
    COMPARE_NUMBER(srcCp, dstCp, checksumOffset);
    COMPARE_NUMBER(srcCp, dstCp, mountElapsedTime);
    COMPARE_MEMORY(srcCp, dstCp, allocType);

    srcInfo_.cpPos += HMFS_BLOCK_SIZE;
    dstInfo_.cpPos += HMFS_BLOCK_SIZE;
    return 0;
}

int32_t CompareCheckPoint::CompareSumCompact()
{
    JournalEntry* srcNatJournal = reinterpret_cast<JournalEntry*>(srcInfo_.cpPos);
    JournalEntry* dstNatJournal = reinterpret_cast<JournalEntry*>(dstInfo_.cpPos);
    if (CompareJournalEntry(srcNatJournal, dstNatJournal, JOURNAL_TYPE_NAT)) {
        HMFS_ERROR("natJournal not equal");
        return -1;
    }

    JournalEntry* srcSitJournal = srcNatJournal + 1;
    JournalEntry* dstSitJournal = dstNatJournal + 1;
    if (CompareJournalEntry(srcSitJournal, dstSitJournal, JOURNAL_TYPE_SIT)) {
        HMFS_ERROR("sitJournal not equal");
        return -1;
    }

    SummaryEntry* srcSumEntry = reinterpret_cast<SummaryEntry*>(srcSitJournal + 1);
    SummaryEntry* dstSumEntry = reinterpret_cast<SummaryEntry*>(dstSitJournal + 1);
    if (CompareSummaryEntry(srcSumEntry, dstSumEntry)) {
        HMFS_ERROR("hot data summary not equal");
        return -1;
    }

    for (int32_t qtype = 0; qtype < MAX_QUOTAS; qtype++) {
        if (!((1 << qtype) & srcInfo_.quotaBits)) {
            continue;
        }

        for (int32_t i = 0; i < QUOTA_DATA_BLOCK_COUNT; i++) {
            srcSumEntry += 1;
            dstSumEntry += 1;
            if (CompareSummaryEntry(srcSumEntry, dstSumEntry)) {
                HMFS_ERROR("quota data summary not equal, qtype = %d, i = %d", qtype, i);
                return -1;
            }
        }
    }

    if (srcInfo_.features & HMFS_FEATURE_LOST_FOUND) {
        srcSumEntry += 1;
        dstSumEntry += 1;
        if (CompareSummaryEntry(srcSumEntry, dstSumEntry)) {
            HMFS_ERROR("lpf data summary not equal");
            return -1;
        }
    }
#ifndef HMFS_UNIT_TEST_RESIZE
    uint32_t reservedLen = HMFS_BLOCK_SIZE - ((char*)(srcSumEntry + 1) - srcInfo_.cpPos);
    if (memcmp((char*)(srcSumEntry + 1), (char*)(dstSumEntry + 1), reservedLen)) {
        HMFS_ERROR("reserved data summary not equal");
        return -1;
    }
#endif
    srcInfo_.cpPos += HMFS_BLOCK_SIZE;
    dstInfo_.cpPos += HMFS_BLOCK_SIZE;

    return 0;
}

int32_t CompareCheckPoint::CompareJournalEntry(JournalEntry *srcJournal, JournalEntry *dstJournal, int journalType)
{
#ifndef HMFS_UNIT_TEST_RESIZE
    if (journalType == JOURNAL_TYPE_NAT) {
        COMPARE_NUMBER(srcJournal, dstJournal, natCount);
        if (CompareNatJournal(&srcJournal->natJournalArray, &dstJournal->natJournalArray)) {
            return -1;
        }
    } else if (journalType == JOURNAL_TYPE_SIT) {
        COMPARE_NUMBER(srcJournal, dstJournal, sitCount);
        if (CompareSitJournal(&srcJournal->sitJournalArray, &dstJournal->sitJournalArray)) {
            return -1;
        }
    }
#endif
    return 0;
}

int32_t CompareCheckPoint::CompareNatJournal(NatJournal *srcNatJournal, NatJournal *dstNatJournal)
{
    for (uint32_t i = 0; i < NAT_JOURNAL_ENTRY_COUNT; i++) {
        COMPARE_NUMBER(srcNatJournal, srcNatJournal, entries[i].nodeId, i);
        COMPARE_NUMBER(srcNatJournal, srcNatJournal, entries[i].natEntry.version, i);
        COMPARE_NUMBER(srcNatJournal, srcNatJournal, entries[i].natEntry.inodeNumber, i);
        COMPARE_NUMBER(srcNatJournal, srcNatJournal, entries[i].natEntry.blockId, i);
    }

    return 0;
}

int32_t CompareCheckPoint::CompareSitJournal(SatJournal *srcSitJournal, SatJournal *dstSitJournal)
{
    for (uint32_t i = 0; i < NAT_JOURNAL_ENTRY_COUNT; i++) {
        COMPARE_NUMBER(srcSitJournal, dstSitJournal, entries[i].sitNumber, i);
        COMPARE_NUMBER(srcSitJournal, dstSitJournal, entries[i].sitEntry.usedBlocks, i);
        COMPARE_MEMORY(srcSitJournal, dstSitJournal, entries[i].sitEntry.blockBitmap, i);
        COMPARE_NUMBER(srcSitJournal, dstSitJournal, entries[i].sitEntry.modificationTime, i);
    }

    return 0;
}

int32_t CompareCheckPoint::CompareSummaryEntry(SummaryEntry *srcSumEntry, SummaryEntry*dstSumEntry)
{
    COMPARE_NUMBER(srcSumEntry, dstSumEntry, nodeId);
    COMPARE_NUMBER(srcSumEntry, dstSumEntry, version);
    COMPARE_NUMBER(srcSumEntry, dstSumEntry, nodeBlockOffset);

    return 0;
}

int32_t CompareCheckPoint::CompareHotNodeSummary()
{
    SummaryBlockData *srcSumBlockData = reinterpret_cast<SummaryBlockData*>(srcInfo_.cpPos);
    SummaryBlockData *dstSumBlockData = reinterpret_cast<SummaryBlockData*>(dstInfo_.cpPos);

    for (uint32_t i = 0; i < ENTRY_COUNT_IN_SUM; i++) {
        if (CompareSummaryEntry(&srcSumBlockData->entries[i], &dstSumBlockData->entries[i])) {
            HMFS_ERROR("entries[%u] in HotNodeSummary not equal", i);
            return -1;
        }
    }

    if (memcmp(&srcSumBlockData->journal, &dstSumBlockData->journal, sizeof(JournalEntry))) {
        HMFS_ERROR("journal in HotNodeSummary not equal");
        return -1;
    }

    COMPARE_NUMBER(srcSumBlockData, dstSumBlockData, footer.entryType);
    COMPARE_NUMBER(srcSumBlockData, dstSumBlockData, footer.checkSum);

    srcInfo_.cpPos += HMFS_BLOCK_SIZE;
    dstInfo_.cpPos += HMFS_BLOCK_SIZE;
    return 0;
}

int32_t CompareCheckPoint::CompareNatBit(char *srcbuf, char *dstCp, uint32_t natBitmapSize, uint32_t natBitsAreablocks)
{
    char *src = srcbuf;
    char *dst = dstCp;

    /* +---------+------------------+-------------------+
     * | crc     | full nat bit map | empty nat bit map |
     * +---------+------------------+-------------------+
     *  uint64_t    natBitmapSize_     natBitmapSize_
     *                 all zero
     */
    src += sizeof(uint64_t);
    dst += sizeof(uint64_t);

    if (memcmp(src, dst, natBitmapSize)) {
        HMFS_ERROR("full nat bit map not equal");
        return -1;
    }
    src += natBitmapSize;
    dst += natBitmapSize;

    if (memcmp(src, dst, natBitmapSize)) {
        HMFS_ERROR("empty nat bit map not equal");
        return -1;
    }
    src += natBitmapSize;
    dst += natBitmapSize;

    uint64_t remainderBufLen = natBitsAreablocks * HMFS_BLOCK_SIZE - (sizeof(uint64_t) + natBitmapSize + natBitmapSize);
    if (memcmp(src, dst, remainderBufLen)) {
        HMFS_ERROR("empty nat bit map not equal");
        return -1;
    }

    srcInfo_.cpPos += natBitsAreablocks * HMFS_BLOCK_SIZE;
    dstInfo_.cpPos += natBitsAreablocks * HMFS_BLOCK_SIZE;

    return 0;
}
} // namespace Hmfs
} // namespace OHOS
